/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022.
 * Description: fast notify for hpvisor
 * Author: lilinjie8 <lilinjie8@huawei.com>
 * Create: 2022-02-10
 */

#ifndef RTOS_VNOTIFY_H
#define RTOS_VNOTIFY_H

#include <linux/refcount.h>
#include <linux/fs.h>
#include <linux/fdtable.h>
#include <linux/eventfd.h>
#include <linux/wait.h>
#include <linux/file.h>
#include <linux/task_struct_extend.h>
#include <linux/hpvisor.h>

#define NO_MODE		0
#define VM_MODE		(1U << 0)
#define UM_MODE		(1U << 1)

/* the maximum of channel count */
#define CHN_NR_LIMIT	(1UL << 20)

#define CHN_PAGE_SHIFT 9
#define CHN_PAGE_NR (1 << CHN_PAGE_SHIFT)

#define CHN_CNT_BITS 16
#define CHN_CNT_MASK ((1UL << CHN_CNT_BITS) - 1)
#define CHN_CNT_MAX ((1UL << (CHN_CNT_BITS - 1)) - 1) /* not include signed bit */

#define STATUS_HOST_SHIFT 0
#define STATUS_HOST_MASK (CHN_CNT_MASK << STATUS_HOST_SHIFT)

#define STATUS_GUEST_SHIFT (STATUS_HOST_SHIFT + CHN_CNT_BITS)
#define STATUS_GUEST_MASK (CHN_CNT_MASK << STATUS_GUEST_SHIFT)

#define HPVISOR_STATE_PENDING_BIT	(1 << 0) /* whether the chn is pending */
#define HPVISOR_STATE_NOTIFYING_BIT	(1 << 1) /* userspace maybe clear it */
#define HPVISOR_STATE_CLOSED_BIT	(1 << 2) /* whether the chn has been closed */
#define HPVISOR_STATE_MASKED_BIT	(1 << 3) /* reserved */
#define HPVISOR_STATE_IS_READY (HPVISOR_STATE_PENDING_BIT | HPVISOR_STATE_NOTIFYING_BIT)

#define MUTEX_FLAGS		0x07

#define CONSUMED_BIT 1
#define HPVISOR_RQ_CHN_ID_BITS	20
#define HPVISOR_RQ_CONSUMED_BIT	(1 << HPVISOR_RQ_CHN_ID_BITS)

enum {
	/* control channels are reserved for qemu and guest */
	CTRL_CHN_QEMU_WAKE_UP_GUEST = 0, /* host send request to guest */
	CTRL_CHN_GUEST_WAKE_UP_QEMU, /* guest send request to host */
	NORMAL_CHN_START_IN_FIRST_STRIDE
};
#define NORMAL_CHN_START_IN_OTHER_STRIDE 0

struct hpvisor_chn_state {
	uint32_t mask;
	uint32_t state;
};

struct hpvisor_ring_queue {
	struct {
		uint32_t count; /* ring_queue buf count */
		uint16_t count_order; /* ring_queue buf count = 2^count_order */
		uint16_t data_width; /* vnoId max bit size */
	} conf;

	uint64_t head;
	uint64_t tail;
};

struct vnotify_data_t {
	u32 mode;
	refcount_t ref_count;

	struct vnotify_data_t *(*get_vnotify)(void);
	void (*put_vnotify)(void);
	int (*task_first_call)(void);
	bool (*check_task_perm)(void);
	void (*vnotify_close_chn)(struct file *filp);
	void (*vnotify_chns_unlock)(void);
	struct chns_struct *(*dup_chns)(struct chns_struct *oldc, struct chns_struct *newc,
		bool *use_chn);
	bool (*chns_struct_free_list)(struct chns_struct *chns);
	void (*ipi_vnotify_handler)(void);
};

struct chns_struct {
	atomic_t count;
	struct kvm *kvm;
	struct mutex lock;
	struct list_head head;
};

struct chn_data_t {
	u32 alloced;
	u32 flags;
	atomic64_t status; /* -1: initial value, 0: dettached, > 0: attach count */

	spinlock_t lock;
	struct file *file;
	spinlock_t file_lock;
};

struct vnotify_ioctl_operations {
	int (*check)(void);
	int (*query)(u32 id);
	int (*ctrl_attach)(void);
	int (*ctrl_detach)(int efd);
	int (*ctrl_send)(int efd);
	int (*guest_attach)(int flags, uintptr_t uname);
	int (*guest_detach)(u32 id);
	int (*host_send)(int efd);
	int (*host_wake)(int efd);
	int (*host_find)(int efd);
	int (*host_attach)(int flags, uintptr_t uname, unsigned long chn_fds);
	int (*host_detach)(int fd0, int fd1, int flags);
	int (*print_record)(int fd);
	long (*get_strides_mem_size)(void);
	long (*get_chn_xid)(u32 id, unsigned long chn_xid);
	long (*get_vnotify_setting)(unsigned long vnotify_setting);
	int (*vnotify_mem_init)(unsigned int chn_nr);
};

void set_vnotify_operations(const struct vnotify_ioctl_operations *oper);
struct file_operations *get_vnotify_cdev_ops(void);

void set_vdata_t(struct vnotify_data_t *vdata_t);
struct vnotify_data_t *get_vdata_t(void);

extern struct chns_struct vnotify_init_chns;

int hpvisor_file_wake_up(struct file *filp, __poll_t event);
extern void hpvisor_vnotify_send_ipi(int cpu);
extern int hpvisor_vnotify_alloc_efd(u32 id, unsigned int count, int efd_flags, int chn_attach_flags);
extern bool hpvisor_file_check_shared(struct file *file);
extern int check_vnotify_file(struct file *filp);

int um_ioctl_vnotify_mem_init_stride(unsigned long arg);

void rq_header_init(struct hpvisor_ring_queue *ring, uint32_t count_order);
void ring_queue_enqueue(struct hpvisor_ring_queue *rq, uint64_t *rq_array, unsigned int id);
int ring_queue_dequeue(struct hpvisor_ring_queue *rq, uint64_t *rq_array);

int copy_vnotify_chns(unsigned long flags, struct rtos_task_struct *tsk, bool *use_chn);
struct chns_struct *chns_alloc_and_init(struct kvm *kvm);
bool exit_vnotify_chns(struct rtos_task_struct *task);

/* Obtain the lock process by referring to open source __mutex_owner */
static inline struct task_struct *hpvisor_mutex_owner(struct mutex *lock)
{
	return (struct task_struct *)(atomic_long_read(&lock->owner) & ~MUTEX_FLAGS);
}

static inline int copy_name_from_user(char *kname, size_t len, void __user *uname)
{
	unsigned long length;

	length = strnlen_user(uname, len);
	if (!length || length > len)
		return -EFAULT;

	if (unlikely(!access_ok(uname, length - 1)))
		return -EFAULT;
	if (copy_from_user(kname, uname, length - 1))
		return -EFAULT;
	kname[length - 1] = 0;
	return 0;
}

static inline void chn_data_lock(struct chn_data_t *data_t)
{
	spin_lock(&data_t->lock);
}

static inline void chn_data_unlock(struct chn_data_t *data_t)
{
	spin_unlock(&data_t->lock);
}

static inline void chn_status_set(struct chn_data_t *data_t, s64 status)
{
	atomic64_set(&data_t->status, status);
}

static inline s64 chn_status_get(const struct chn_data_t *data_t)
{
	return atomic64_read(&data_t->status);
}

static inline int get_efd_flags(int flags)
{
	/* if channel shared, efd read will clear one count, need EFD_SEMAPHORE
	 * if channel not shared, efd read will clear all count
	 */
	if (flags & HPVISOR_ATTACH_SHARED_BIT)
		return EFD_NONBLOCK | EFD_SEMAPHORE;
	return EFD_NONBLOCK;
}

static inline int create_new_eventfd(u32 id, int efd_flags, int chn_attach_flags)
{
	return hpvisor_vnotify_alloc_efd(id, 0, efd_flags, chn_attach_flags);
}

int new_chn_data_efd_attach(struct chn_data_t *data_t, int fd);

static inline void chn_data_efd_attach(struct chn_data_t *data_t, int fd)
{
	fd_install(fd, data_t->file);
	get_file(data_t->file);
}

static inline s16 status_host_cnt(u64 status)
{
	return (status >> STATUS_HOST_SHIFT) & CHN_CNT_MASK;
}

static inline s16 status_guest_cnt(u64 status)
{
	return (status >> STATUS_GUEST_SHIFT) & CHN_CNT_MASK;
}

static inline int get_host_attach_state(const struct chn_data_t *data_t)
{
	s64 status;

	status = chn_status_get(data_t);
	if (status_host_cnt(status) == 0 && !(data_t->flags & HPVISOR_ATTACH_PERSISTENT_BIT))
		return HPVISOR_CHN_HOST_DETACH;
	else if (status_host_cnt(status) > 0)
		return HPVISOR_CHN_HOST_ATTACH;
	return 0;
}

static inline int get_guest_attach_state(const struct chn_data_t *data_t)
{
	s64 status;

	status = chn_status_get(data_t);
	if (status_guest_cnt(status) == 0 && !(data_t->flags & HPVISOR_ATTACH_PERSISTENT_BIT))
		return HPVISOR_CHN_GUEST_DETACH;
	else if (status_guest_cnt(status) > 0)
		return HPVISOR_CHN_GUEST_ATTACH;
	return 0;
}

static inline int get_data_status(const struct chn_data_t *data_t)
{
	return get_host_attach_state(data_t) | get_guest_attach_state(data_t);
}

#endif /* RTOS_VNOTIFY_H */
